package com.work.demo.file.sort;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 排序
 *
 * @author Adrian
 * @date 2020/12/8 16:02
 */
public class SelfSort {
	/**
	 * 生成百万行数据
	 */
	private static final Long GENERATE_ROW = 100000L;

	/**
	 * 拆分小文件，每个小文件行数
	 */
	private static final Integer MAX_ROW = 10000;
	/**
	 * 基础文件夹
	 */
	private static final String BASE_FOLDER = "d:/test/";

	public static void main(String[] args) {
		deleteFolderFiles(BASE_FOLDER);
		String bigTxt = BASE_FOLDER + "big.txt";
		String splitFolder = BASE_FOLDER + "/folder/";
		String outSortFile = BASE_FOLDER + "sort.txt";
		List<File> files = null;
		try {
			randomData(bigTxt);
			files = splitDataToSaveFile(bigTxt, splitFolder);
			sortSmallFiles(files);
		} catch (IOException e) {
			e.printStackTrace();
		}
		List<SmallFileInfo> fileInfos = getSmallFileBufferedReader(files);
		try {
			mergeSort(outSortFile, fileInfos);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			fileInfos.forEach(t -> {
				BufferedReader reader = t.getReader();
				if (null != reader) {
					try {
						reader.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			});
		}
		deleteFolderFiles(splitFolder);
	}


	/**
	 * 删除文件夹下所有文件
	 *
	 * @param folderPath
	 */
	public static void deleteFolderFiles(String folderPath) {
		File folderFile = new File(folderPath);
		if (!folderFile.exists() || !folderFile.isDirectory()) {
			folderFile.mkdirs();
		}
		File[] files = folderFile.listFiles();
		if (null != files && files.length > 0) {
			for (File f : files) {
				f.delete();
			}
		}
	}

	/**
	 * 模拟生成数据
	 */
	public static void randomData(String randFilePath) throws IOException {
		System.out.println("=========开始生成数据==========");
		long startTime = System.currentTimeMillis();

		File file = new File(randFilePath);
		try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) {
			if (file.exists()) {
				if (file.isDirectory()) {
					throw new IOException("File '" + file + "' exists but is a directory");
				}
				if (!file.canWrite()) {
					throw new IOException("File '" + file + "' cannot be written to");
				}
			} else {
				File parent = file.getParentFile();
				if (parent != null && !parent.exists() && !parent.mkdirs()) {
					throw new IOException("File '" + file + "' could not be created");
				}
			}

			Random random = new Random();
			for (long i = 0; i < GENERATE_ROW; i++) {
				long value = random.nextLong();
				value = value < 0 ? -value : value;
				//写入并换行
				bufferedWriter.write(value + "\r\n");
			}
		}
		long endTime = System.currentTimeMillis();
		System.out.println("生成结束，耗时：" + (endTime - startTime) / 1000 + "秒");
	}

	public static List<File> splitDataToSaveFile(String sourceFilePath, String targetDirectoryPath) throws IOException {
		long startTime = System.currentTimeMillis();
		System.out.println("=========开始分割文件==========");
		List<File> fileList = new ArrayList<>();
		File sourceFile = new File(sourceFilePath);
		File targetFile = new File(targetDirectoryPath);
		if (sourceFile.exists()) {
			if (sourceFile.isDirectory()) {
				throw new IOException("File '" + sourceFile + "' exists but is a directory");
			}
			if (!sourceFile.canWrite()) {
				throw new IOException("File '" + sourceFile + "' cannot be written to");
			}
		}
		if (targetFile.exists()) {
			if (!targetFile.isDirectory()) {
				throw new IOException("File '" + targetFile + "' exists but is a directory");
			}
		} else {
			targetFile.mkdirs();
		}
		try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(sourceFile.toPath())))) {
			StringBuilder stringBuilder = new StringBuilder();
			String lineStr;
			int lineNo = 1, fileNum = 1;
			while ((lineStr = bufferedReader.readLine()) != null) {
				stringBuilder.append(lineStr.trim()).append("\r\n");
				if (lineNo % MAX_ROW == 0) {
					File file = new File(targetDirectoryPath + File.separator + fileNum + sourceFile.getName());
					writeFile(stringBuilder.toString(), file, false);
					//清空文本
					stringBuilder.setLength(0);
					fileNum++;
					fileList.add(file);
				}
				lineNo++;
			}
			if ((lineNo - 1) % MAX_ROW != 0) {
				File file = new File(targetDirectoryPath + File.separator + fileNum + sourceFile.getName());
				writeFile(stringBuilder.toString(), file, false);
				fileList.add(file);
			}

			long endTime = System.currentTimeMillis();
			System.out.println("分割文件结束，耗时：" + (endTime - startTime) / 1000 + "秒");
		} catch (Exception e) {
			System.out.println("分割文件异常:");
			e.printStackTrace();
		}
		return fileList;
	}

	/**
	 * 对小文件排序
	 *
	 * @param files
	 * @throws IOException
	 */
	public static void sortSmallFiles(List<File> files) throws IOException {
		long startTime = System.currentTimeMillis();
		System.out.println("=========开始对所有小文件内部排序==========");
		for (File file : files) {
			List<Long> list = new ArrayList<>();
			String lineStr = null;
			try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(file.toPath())))) {
				while ((lineStr = bufferedReader.readLine()) != null) {
					list.add(Long.parseLong(lineStr.trim()));
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			Collections.sort(list);
			String collect = list.stream().map(String::valueOf).collect(Collectors.joining("\r\n"));
			writeFile(collect, file, false);
		}
		long endTime = System.currentTimeMillis();
		System.out.println("所有小文件内部排序结束，耗时：" + (endTime - startTime) / 1000 + "秒");
	}

	/**
	 * 排序合并所有小文件
	 *
	 * @param outSortFile
	 * @param fileInfos
	 */
	public static void mergeSort(String outSortFile, List<SmallFileInfo> fileInfos) throws IOException {
		long startTime = System.currentTimeMillis();
		System.out.println("=========开始对所有小文件合并并排序==========");
		File outFile = new File(outSortFile);
		File parent = outFile.getParentFile();
		if (parent != null && !parent.exists() && !parent.mkdirs()) {
			throw new IOException("File '" + outFile + "' could not be created");
		}
		if (!outFile.exists() || outFile.isDirectory()) {
			outFile.createNewFile();
		}
		// 升序比较器
		Comparator<Map.Entry<SmallFileInfo, Long>> valueComparator = Map.Entry.comparingByValue();
		for (int i = 0; i < GENERATE_ROW; i++) {
//            System.out.println("i=======" + i);
			Map<SmallFileInfo, Long> map = new HashMap<>();
			for (SmallFileInfo fileInfo : fileInfos) {
				String nextValue = fileInfo.readNextValue();
				if (null != nextValue && !"".equals(nextValue)) {
					map.put(fileInfo, Long.parseLong(fileInfo.getValue()));
				}
			}
			// map转换成list进行排序
			List<Map.Entry<SmallFileInfo, Long>> list = new ArrayList<>(map.entrySet());
			// 排序
			list.sort(valueComparator);
			Map.Entry<SmallFileInfo, Long> fileLongEntry = list.get(0);
			SmallFileInfo key = fileLongEntry.getKey();
			Long value = fileLongEntry.getValue();
			key.setValueIsUse(true);
			writeFile(value + "\r\n", outFile, true);
		}

		long endTime = System.currentTimeMillis();
		System.out.println("所有小文件合并排序结束，耗时：" + (endTime - startTime) / 1000 + "秒");
	}

	/**
	 * 获取文件引用，为下一步文件合并排序做准备
	 *
	 * @param files
	 * @return
	 */
	public static List<SmallFileInfo> getSmallFileBufferedReader(List<File> files) {
		List<SmallFileInfo> fileInfos = new ArrayList<>();
		for (File file : files) {
			if (file.exists()) {
				BufferedReader bufferedReader = null;
				try {
					bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
				} catch (FileNotFoundException e) {
					e.printStackTrace();
					continue;
				}
				SmallFileInfo smallFileInfo = new SmallFileInfo();
				smallFileInfo.setLineNum(0L);
				smallFileInfo.setValue(null);
				smallFileInfo.setValueIsUse(true);
				smallFileInfo.setReader(bufferedReader);
				smallFileInfo.setFileAbsolutePath(file.getAbsolutePath());
				fileInfos.add(smallFileInfo);
			}
		}
		return fileInfos;
	}


	/**
	 * @param text     要写入的内容
	 * @param file     目标文件
	 * @param isAppend true为追加写入，false为覆盖写入
	 */
	private static void writeFile(String text, File file, Boolean isAppend) {
		try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, isAppend)), 1024)) {
			bufferedWriter.write(text);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

